using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Urban_Flash.Player
{
    

    #region Enums
    /// <summary>
    /// Place the labels for the Transitions in this enum.
    /// </summary>
    public enum Transition
    {
        NullTransition = 0, // Use this transition to represent a non-existing transition in your system
        NoActions, //Tranistions to idle
        StartRunning, StartJump, StartFalling,
    }

    /// <summary>
    /// Place the labels for the States in this enum.
    /// </summary>
    public enum StateID
    {
        NullStateID = 0, // Use this ID to represent a non-existing State in your system    
        Idle, Run, Jump, Fall
    }
    #endregion

    /// <summary>
    /// The Finite State Machine class and has a List with the States the character has and methods to add,
    ///  delete a state, and to change the current state the Machine is on.
    /// </summary>
   public class FiniteStateMachine
    {
        #region Fields
        private List<FSMState> states;
        private StateID currentStateID;
        private FSMState currentState;
        #endregion

        #region Properties
        // The only way one can change the state of the FSM is by performing a transition
        // Don't change the CurrentState directly
        public StateID CurrentStateID { get { return currentStateID; } }
        public FSMState CurrentState { get { return currentState; } }
        #endregion

        #region Constructor
        public FiniteStateMachine()
        {
            states = new List<FSMState>();
        }
        #endregion

        #region Methods
        /// <summary>
        /// This method places new states inside the FSM,
        /// or prints an ERROR message if the state was already inside the List.
        /// First state added is also the initial state.
        /// </summary>
        public void AddState(FSMState s)
        {
            // Check for Null reference before deleting
            if (s == null)
            {
                throw new Exception("FSM ERROR: Null reference is not allowed");
            }

            // First State inserted is also the Initial state,
            //   the state the machine is in when the simulation begins
            if (states.Count == 0)
            {
                states.Add(s);
                currentState = s;
                currentStateID = s.ID;
                return;
            }

            // Add the state to the List if it's not inside it
            foreach (FSMState state in states)
            {
                if (state.ID == s.ID)
                {
                    throw new Exception("FSM ERROR: Impossible to add state " + s.ID.ToString() + " because state has already been added");
                }
            }
            states.Add(s);
        }

        /// <summary>
        /// This method delete a state from the FSM List if it exists, 
        ///   or prints an ERROR message if the state was not on the List.
        /// </summary>
        public void DeleteState(StateID id)
        {
            // Check for NullState before deleting
            if (id == StateID.NullStateID)
            {
                throw new Exception("FSM ERROR: NullStateID is not allowed for a real state");
            }

            // Search the List and delete the state if it's inside it
            foreach (FSMState state in states)
            {
                if (state.ID == id)
                {
                    states.Remove(state);
                    return;
                }
            }
            throw new Exception("FSM ERROR: Impossible to delete state " + id.ToString() + ". It was not on the list of states");
        }

        /// <summary>
        /// This method tries to change the state the FSM is in based on
        /// the current state and the transition passed. If current state
        ///  doesn't have a target state for the transition passed, 
        /// an ERROR message is printed.
        /// </summary>
        public void PerformTransition(Transition trans)
        {
            // Check for NullTransition before changing the current state
            if (trans == Transition.NullTransition)
            {
                throw new Exception("FSM ERROR: NullTransition is not allowed for a real transition");
            }

            // Check if the currentState has the transition passed as argument
            StateID id = currentState.GetOutputState(trans);
            if (id == StateID.NullStateID)
            {
                return;
               //throw new Exception("FSM ERROR: State " + currentStateID.ToString() + " does not have a target state " + " for transition " + trans.ToString());
            }

            // Update the currentStateID and currentState      
            currentStateID = id;
            foreach (FSMState state in states)
            {
                if (state.ID == currentStateID)
                {
                    // Do the post processing of the state before setting the new one
                    currentState.DoBeforeLeaving();

                    currentState = state;

                    // Reset the state to its desired condition before it can reason or act
                    currentState.DoBeforeEntering();
                    break;
                }
            }
        }
        #endregion
    }

    /// <summary>
    /// This class represents the States in the Finite State System.
    /// Each state has a Dictionary with pairs (transition-state) showing
    /// which state the FSM should be if a transition is fired while this state
    /// is the current state.
    /// </summary>
    public abstract class FSMState
    {
        #region Fields
        protected Dictionary<Transition, StateID> transitionStates = new Dictionary<Transition, StateID>();
        protected StateID stateID;
        protected Player Controller;
        public event PlayerStateEventHandler EventPlayerInput;
        #endregion

        #region Constructor
        public FSMState(Player StateController)
        {
            Controller = StateController;
            EventPlayerInput += new PlayerStateEventHandler(PlayerInput);
        }
        #endregion

        #region Properties
        public StateID ID { get { return stateID; } }
        #endregion

        #region Methods
        public void AddTransition(Transition trans, StateID id)
        {
            // Check if anyone of the args is invalid
            if (trans == Transition.NullTransition)
            {
                throw new Exception("FSMState ERROR: NullTransition is not allowed for a real transition");
            }

            if (id == StateID.NullStateID)
            {
                throw new Exception("FSMState ERROR: NullStateID is not allowed for a real ID");
            }

            //   check if the current transition was already inside the map
            if (transitionStates.ContainsKey(trans))
            {
                throw new Exception("FSMState ERROR: State " + stateID.ToString() + " already has transition " + trans.ToString() + "Impossible to assign to another state");
            }

            transitionStates.Add(trans, id);
        }

        /// <summary>
        /// This method deletes a pair transition-state from this state's map.
        /// If the transition was not inside the state's map, an ERROR message is printed.
        /// </summary>
        public void DeleteTransition(Transition trans)
        {
            // Check for NullTransition
            if (trans == Transition.NullTransition)
            {
                throw new Exception("FSMState ERROR: NullTransition is not allowed");
            }

            // Check if the pair is inside the map before deleting
            if (transitionStates.ContainsKey(trans))
            {
                transitionStates.Remove(trans);
                return;
            }
            throw new Exception("FSMState ERROR: Transition " + trans.ToString() + " passed to " + stateID.ToString() + " was not on the state's transition list");
        }

        /// <summary>
        /// This method returns the new state the FSM should be if
        ///    this state receives a transition and 
        /// </summary>
        public StateID GetOutputState(Transition trans)
        {
            // Check if the map has this transition
            if (transitionStates.ContainsKey(trans))
            {
                return transitionStates[trans];
            }
            return StateID.NullStateID;
        }
        #endregion

        #region Virtual Methods
        /// <summary>
        /// This method is used to set up the State condition before entering it.
        /// It is called automatically by the FSMSystem class before assigning it
        /// to the current state.
        /// </summary>
        public virtual void DoBeforeEntering() { }

        /// <summary>
        /// This method is used to make anything necessary, as reseting variables
        /// before the FSMSystem changes to another one. It is called automatically
        /// by the FSMSystem before changing to a new state.
        /// </summary>
        public virtual void DoBeforeLeaving() { }

        /// <summary>
        /// This method decides if the state should transition to another on its list
        /// </summary>
        public virtual void Execute() { }

        /// <summary>
        /// This function handles events sent by the player. Specifically for input 
        /// (i.e. Player presses A for jump and inturn sends an event to the current state.
        /// </summary>
        /// <param name="sender">The player who sent the the event</param>
        /// <param name="e">The information the player has sent in to be handled</param>
        public virtual void PlayerInput(object sender, PlayerStateEventArgs e) { }

        /// <summary>
        /// The player calls this function to send the event
        /// </summary>
        public void OnPlayerInputEvent(object sender, PlayerStateEventArgs e)
        {
            if (EventPlayerInput != null)
                EventPlayerInput(sender, e);
        } 
        #endregion

    }
}
